/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { TmpBase } from './tmp_base';
import { Arg } from './arg';
import { FP, GP } from './symbols';

const REG_SOME_VALUE_2: number = 2;
const REG_SOME_VALUE_3: number = 3;
const REG_SOME_VALUE_4: number = 4;
const REG_SOME_VALUE_5: number = 5;
const REG_SOME_VALUE_6: number = 6;
const REG_SOME_VALUE_7: number = 7;
const REG_SOME_VALUE_8: number = 8;
const REG_SOME_VALUE_9: number = 9;
const REG_SOME_VALUE_10: number = 10;
const REG_SOME_VALUE_11: number = 11;
const REG_SOME_VALUE_12: number = 12;
const REG_SOME_VALUE_13: number = 13;
const REG_SOME_VALUE_14: number = 14;
const REG_SOME_VALUE_15: number = 15;

export class Reg extends TmpBase {
  _index: number;
  _type: string;
  _name: string;
  _isCalleeSave: boolean;
  value: number | null;

  static regs: Reg[] = new Array();

  static rax = Reg.newReg(0, GP, 'rax', false);
  static rcx = Reg.newReg(1, GP, 'rcx', false);
  static rdx = Reg.newReg(REG_SOME_VALUE_2, GP, 'rdx', false);
  static rbx = Reg.newReg(REG_SOME_VALUE_3, GP, 'rbx', true);
  static rsp = Reg.newReg(REG_SOME_VALUE_4, GP, 'rsp', false);
  static rbp = Reg.newReg(REG_SOME_VALUE_5, GP, 'rbp', true);
  static rsi = Reg.newReg(REG_SOME_VALUE_6, GP, 'rsi', false);
  static rdi = Reg.newReg(REG_SOME_VALUE_7, GP, 'rdi', false);
  static r8 = Reg.newReg(REG_SOME_VALUE_8, GP, 'r8', false);
  static r9 = Reg.newReg(REG_SOME_VALUE_9, GP, 'r9', false);
  static r10 = Reg.newReg(REG_SOME_VALUE_10, GP, 'r10', false);
  static r11 = Reg.newReg(REG_SOME_VALUE_11, GP, 'r11', false);
  static r12 = Reg.newReg(REG_SOME_VALUE_12, GP, 'r12', true);
  static r13 = Reg.newReg(REG_SOME_VALUE_13, GP, 'r13', true);
  static r14 = Reg.newReg(REG_SOME_VALUE_14, GP, 'r14', true);
  static r15 = Reg.newReg(REG_SOME_VALUE_15, GP, 'r15', true);
  static xmm0 = Reg.newReg(0, FP, 'xmm0', false);
  static xmm1 = Reg.newReg(1, FP, 'xmm1', false);
  static xmm2 = Reg.newReg(REG_SOME_VALUE_2, FP, 'xmm2', false);
  static xmm3 = Reg.newReg(REG_SOME_VALUE_3, FP, 'xmm3', false);
  static xmm4 = Reg.newReg(REG_SOME_VALUE_4, FP, 'xmm4', false);
  static xmm5 = Reg.newReg(REG_SOME_VALUE_5, FP, 'xmm5', false);
  static xmm6 = Reg.newReg(REG_SOME_VALUE_6, FP, 'xmm6', false);
  static xmm7 = Reg.newReg(REG_SOME_VALUE_7, FP, 'xmm7', false);
  static xmm8 = Reg.newReg(REG_SOME_VALUE_8, FP, 'xmm8', false);
  static xmm9 = Reg.newReg(REG_SOME_VALUE_9, FP, 'xmm9', false);
  static xmm10 = Reg.newReg(REG_SOME_VALUE_10, FP, 'xmm10', false);
  static xmm11 = Reg.newReg(REG_SOME_VALUE_11, FP, 'xmm11', false);
  static xmm12 = Reg.newReg(REG_SOME_VALUE_12, FP, 'xmm12', false);
  static xmm13 = Reg.newReg(REG_SOME_VALUE_13, FP, 'xmm13', false);
  static xmm14 = Reg.newReg(REG_SOME_VALUE_14, FP, 'xmm14', false);
  static xmm15 = Reg.newReg(REG_SOME_VALUE_15, FP, 'xmm15', false);

  static callFrameRegister = Reg.rbp;
  static stackPointerRegister = Reg.rsp;

  static get gprs(): Reg[] {
    let arr: Reg[] = new Array();
    for (let i = 0; i < Reg.regs.length; i++) {
      let reg = Reg.regs[i];
      if (reg.isGP) {
        arr.push(reg);
      }
    }
    return arr;
  }

  static get fprs(): Reg[] {
    let arr: Reg[] = new Array();
    for (let i = 0; i < Reg.regs.length; i++) {
      let reg = Reg.regs[i];
      if (!reg.isGP) {
        arr.push(reg);
      }
    }
    return arr;
  }

  static get calleeSaveGPRs(): Reg[] {
    let arr: Reg[] = new Array();
    for (let i = 0; i < Reg.regs.length; i++) {
      let reg = Reg.regs[i];
      if (reg.isGP && reg.isCalleeSave) {
        arr.push(reg);
      }
    }
    return arr;
  }

  static get calleeSaveFPRs(): Reg[] {
    let arr: Reg[] = new Array();
    for (let i = 0; i < Reg.regs.length; i++) {
      let reg = Reg.regs[i];
      if (!reg.isGP && reg.isCalleeSave) {
        arr.push(reg);
      }
    }
    return arr;
  }

  static get calleeSaves(): Reg[] {
    let arr: Reg[] = new Array();
    for (let i = 0; i < Reg.regs.length; i++) {
      let reg = Reg.regs[i];
      if (reg.isCalleeSave) {
        arr.push(reg);
      }
    }
    return arr;
  }

  constructor(index: number, type: string, name: string, isCalleeSave: boolean) {
    super(type);
    this._index = index;
    this._type = type;
    this._name = name;
    this._isCalleeSave = isCalleeSave;
    super.type = this._type;
    super.isReg = true;
  }

  static fromReg(reg: Reg): Reg {
    return reg;
  }
  get index(): number {
    return this._index;
  }

  get name(): string {
    return this._name;
  }
  get isCalleeSave(): boolean {
    return this._isCalleeSave;
  }

  hash(): number {
    if (this.isGP) {
      return 1 + this._index;
    } else {
      return -1 - this._index;
    }
  }
  toString(): string {
    return `%${this._name}`;
  }
  static extract(arg: Reg): Reg | null {
    if (arg.isReg) {
      let argss = arg.reg();
      return argss as Reg;
    } else {
      return null;
    }
  }
  static forEachFast(arg: Arg, fun: (reg: Reg) => Reg | null): Arg | null {
    return arg.forEachTmpFast(tmp => {
      let tmps = tmp as Reg;
      if (tmps !== null) {
        if (!tmps.isReg) {
          return null;
        }
        return fun(tmps);
      }
      return null;
    });
  }
  static forEach(arg: Arg, argRole: string, argType: string, argWidth: number, fun: (Reg, string, String, number) => Reg | null): Arg | null {
    return arg.forEachTmp(argRole, argType, argWidth, (tmp, role, type, width) => {
      if (!tmp.isReg) {
        return null;
      }
      return fun(tmp, role, type, width);
    });
  }
  static newReg(index: number = 0, type: string = 'GP', name: string, isCalleeSave: boolean = false): Reg {
    let result = new Reg(index, type, name, isCalleeSave);
    this.regs.push(result);
    return result;
  }
}
